package icu.xblogs.report.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

/**
 * @author Zhumingming
 * @title: PagePlugin
 * @projectName Xblog
 * @description: 分页拦截器
 * @date 2020/8/7
 *
 * 使用分页插件步骤
 * 1. 本项目的分页查询的逻辑会在mapper类中单独写出
 * 2. 分页的实体工具类有三个参数，index用于记录分页的开始 size用于记录每页多少条记录
 * 3. 在mapper接口的方法中，有Page参数的都是分页方法，在mapper中无需再写分页的sql逻辑，插件已经自动的完成
 * 4. 如果有多个参数，在非page参数前加上@param
 */
@Intercepts(@Signature(type = StatementHandler.class,
        method = "prepare", args = {Connection.class,
        Integer.class}))
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //1. 检测当前是否满足分页条件

        // 带上分页参数,获取被拦截的对象StatementHandler，不能去拦截exector,因为需要拦截参数传递
        StatementHandler target = (StatementHandler) invocation.getTarget();
        // SQL包 sql、参数、参数映射，
        // 如果参数为单一的参数且没有配置@param注解，且参数值不是复杂的类型，会被判定为arg，如果是复杂的类型会为类型值，
        // 参数为多个 类型则会被判定为一个map 键的规则如上，
        // sql为直接可以执行的sql
        BoundSql boundSql = target.getBoundSql();
        Object parameterObject = boundSql.getParameterObject();

        Page page = null;
        //如果参数只有一个Page的类型的时候，直接拿到这个page
        if (parameterObject instanceof Page) {
            page = (Page) parameterObject;
        } else if (parameterObject instanceof Map) {
            //这个流式的编程没看懂，估计是拿到map中Page的类容
            page = (Page) ((Map) parameterObject).values().stream().filter(v -> v instanceof Page).findFirst().orElse(null);
        }
        //如果不是分页  直接放行
        if (page == null) {
            return invocation.proceed();
        }
        // 2.设置总行数
        // select count(0) from (sql);
        page.setTotal(selectCount(invocation));

        // 3.修改原有SQL
        // select * from users offset 0,limit 50
        //重新的设置boundsql中的可执行sql的值
        String newSql= String.format("%s limit %s offset %s", boundSql.getSql(),page.getSize(),page.getOffset());
        //使用MetaObject工具类重新的构建sql
        SystemMetaObject.forObject(boundSql).setValue("sql",newSql);
        return invocation.proceed();
    }

    private int selectCount(Invocation invocation) throws SQLException {
        int count = 0;
        StatementHandler target = (StatementHandler) invocation.getTarget();
        // SQL包 sql、参数、参数映射
        //通过jdbc计算查询sql的总条数
        String countSql = String.format("select count(*) from (%s) as _page", target.getBoundSql().getSql());


        // JDBC
        Connection connection = (Connection) invocation.getArgs()[0];
        PreparedStatement preparedStatement = connection.prepareStatement(countSql);

        target.getParameterHandler().setParameters(preparedStatement);

        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next()) {
            count = resultSet.getInt(1);
        }
        resultSet.close();
        preparedStatement.close();
        return count;
    }

    //包装  动态代理 静态代理
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
